Commit 6c49c27b authored by tommycli@chromium.org's avatar tommycli@chromium.org

Media Galleries API Metadata: Add audio video metadata extractor.

This CL adds a class for extraction of audio and video metadata using ffmpeg.

This is needed to implement the Media Galleries Metadata API for Chrome extensions.

BUG=318450

Review URL: https://codereview.chromium.org/121383002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@243470 0039d316-1c4b-4281-b951-d872f2087c98
parent a7af82ac
...@@ -69,6 +69,24 @@ namespace mediaGalleries { ...@@ -69,6 +69,24 @@ namespace mediaGalleries {
dictionary MediaMetadata { dictionary MediaMetadata {
// The browser sniffed mime type. // The browser sniffed mime type.
DOMString mimeType; DOMString mimeType;
// Defined for images and video. In pixels.
long? height;
long? width;
// Defined for audio and video. In seconds.
double? duration;
// Generic metadata tags.
DOMString? album;
DOMString? artist;
DOMString? comment;
DOMString? copyright;
long? disc;
DOMString? genre;
DOMString? language;
DOMString? title;
long? track;
}; };
callback MediaMetadataCallback = void (MediaMetadata metadata); callback MediaMetadataCallback = void (MediaMetadata metadata);
......
...@@ -4,14 +4,34 @@ ...@@ -4,14 +4,34 @@
#include "chrome/utility/media_galleries/media_metadata_parser.h" #include "chrome/utility/media_galleries/media_metadata_parser.h"
#include <string>
#include "base/bind.h" #include "base/bind.h"
#include "base/message_loop/message_loop.h" #include "base/message_loop/message_loop.h"
#include "base/strings/string_util.h"
#include "media/base/audio_video_metadata_extractor.h"
#include "media/base/data_source.h"
namespace metadata { namespace metadata {
MediaMetadataParser::MediaMetadataParser(DataReader* reader, namespace {
void SetStringScopedPtr(const std::string& value,
scoped_ptr<std::string>* destination) {
if (!value.empty())
destination->reset(new std::string(value));
}
void SetIntScopedPtr(int value, scoped_ptr<int>* destination) {
if (value >= 0)
destination->reset(new int(value));
}
} // namespace
MediaMetadataParser::MediaMetadataParser(media::DataSource* source,
const std::string& mime_type) const std::string& mime_type)
: reader_(reader), : source_(source),
metadata_(new MediaMetadata) { metadata_(new MediaMetadata) {
metadata_->mime_type = mime_type; metadata_->mime_type = mime_type;
} }
...@@ -22,8 +42,41 @@ void MediaMetadataParser::Start(const MetadataCallback& callback) { ...@@ -22,8 +42,41 @@ void MediaMetadataParser::Start(const MetadataCallback& callback) {
DCHECK(callback_.is_null()); DCHECK(callback_.is_null());
callback_ = callback; callback_ = callback;
// TODO(tommycli): Implement for various mime types. if (StartsWithASCII(metadata_->mime_type, "audio/", true) ||
StartsWithASCII(metadata_->mime_type, "video/", true)) {
PopulateAudioVideoMetadata();
}
// TODO(tommycli): Implement for image mime types.
callback_.Run(metadata_.Pass()); callback_.Run(metadata_.Pass());
} }
void MediaMetadataParser::PopulateAudioVideoMetadata() {
DCHECK(source_);
DCHECK(metadata_.get());
media::AudioVideoMetadataExtractor extractor;
if (!extractor.Extract(source_))
return;
if (extractor.duration() >= 0)
metadata_->duration.reset(new double(extractor.duration()));
if (extractor.height() >= 0 && extractor.width() >= 0) {
metadata_->height.reset(new int(extractor.height()));
metadata_->width.reset(new int(extractor.width()));
}
SetStringScopedPtr(extractor.artist(), &metadata_->artist);
SetStringScopedPtr(extractor.album(), &metadata_->album);
SetStringScopedPtr(extractor.artist(), &metadata_->artist);
SetStringScopedPtr(extractor.comment(), &metadata_->comment);
SetStringScopedPtr(extractor.copyright(), &metadata_->copyright);
SetIntScopedPtr(extractor.disc(), &metadata_->disc);
SetStringScopedPtr(extractor.genre(), &metadata_->genre);
SetStringScopedPtr(extractor.language(), &metadata_->language);
SetStringScopedPtr(extractor.title(), &metadata_->title);
SetIntScopedPtr(extractor.track(), &metadata_->track);
}
} // namespace metadata } // namespace metadata
...@@ -9,19 +9,11 @@ ...@@ -9,19 +9,11 @@
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "chrome/common/extensions/api/media_galleries.h" #include "chrome/common/extensions/api/media_galleries.h"
namespace metadata { namespace media {
class DataSource;
// This interface provides bytes needed by MediaMetadataParser. }
class DataReader {
public:
typedef base::Callback<void(const std::string& bytes)> ReadBytesCallback;
virtual ~DataReader() {}
// Called on the utility thread. namespace metadata {
virtual void ReadBytes(int64 offset, int64 length,
const ReadBytesCallback& callback) = 0;
};
// This class takes a MIME type and data source and parses its metadata. It // This class takes a MIME type and data source and parses its metadata. It
// handles audio, video, and images. It delegates its operations to FFMPEG, // handles audio, video, and images. It delegates its operations to FFMPEG,
...@@ -33,10 +25,10 @@ class MediaMetadataParser { ...@@ -33,10 +25,10 @@ class MediaMetadataParser {
typedef extensions::api::media_galleries::MediaMetadata MediaMetadata; typedef extensions::api::media_galleries::MediaMetadata MediaMetadata;
typedef base::Callback<void(scoped_ptr<MediaMetadata>)> MetadataCallback; typedef base::Callback<void(scoped_ptr<MediaMetadata>)> MetadataCallback;
// Takes ownership of |reader|. The MIME type is always sniffed in the browser // Does not take ownership of |source|. The MIME type is sniffed in the
// process, as it's faster (and safe enough) to sniff it there. When the user // browser process, as it's faster (and safe enough). When the user wants
// wants more metadata than just the MIME type, this class provides it. // more metadata than just the MIME type, this class provides it.
MediaMetadataParser(DataReader* reader, const std::string& mime_type); MediaMetadataParser(media::DataSource* source, const std::string& mime_type);
~MediaMetadataParser(); ~MediaMetadataParser();
...@@ -44,7 +36,9 @@ class MediaMetadataParser { ...@@ -44,7 +36,9 @@ class MediaMetadataParser {
void Start(const MetadataCallback& callback); void Start(const MetadataCallback& callback);
private: private:
scoped_ptr<DataReader> reader_; void PopulateAudioVideoMetadata();
media::DataSource* source_;
MetadataCallback callback_; MetadataCallback callback_;
......
// 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 "media/base/audio_video_metadata_extractor.h"
#include "base/bind.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "media/ffmpeg/ffmpeg_common.h"
#include "media/filters/blocking_url_protocol.h"
#include "media/filters/ffmpeg_glue.h"
namespace media {
namespace {
void OnError(bool* succeeded) {
*succeeded = false;
}
// Returns true if the |tag| matches |expected_key|.
bool ExtractString(AVDictionaryEntry* tag, const char* expected_key,
std::string* destination) {
if (!LowerCaseEqualsASCII(std::string(tag->key), expected_key))
return false;
if (destination->empty())
*destination = tag->value;
return true;
}
// Returns true if the |tag| matches |expected_key|.
bool ExtractInt(AVDictionaryEntry* tag, const char* expected_key,
int* destination) {
if (!LowerCaseEqualsASCII(std::string(tag->key), expected_key))
return false;
int temporary = -1;
if (*destination < 0 && base::StringToInt(tag->value, &temporary) &&
temporary >= 0) {
*destination = temporary;
}
return true;
}
} // namespace
AudioVideoMetadataExtractor::AudioVideoMetadataExtractor()
: extracted_(false),
duration_(-1),
width_(-1),
height_(-1),
disc_(-1),
track_(-1) {
}
AudioVideoMetadataExtractor::~AudioVideoMetadataExtractor() {
}
bool AudioVideoMetadataExtractor::Extract(DataSource* source) {
DCHECK(!extracted_);
bool read_ok = true;
media::BlockingUrlProtocol protocol(source, base::Bind(&OnError, &read_ok));
media::FFmpegGlue glue(&protocol);
AVFormatContext* format_context = glue.format_context();
if (!glue.OpenContext())
return false;
if (!read_ok)
return false;
if (!format_context->iformat)
return false;
if (avformat_find_stream_info(format_context, NULL) < 0)
return false;
if (format_context->duration != AV_NOPTS_VALUE)
duration_ = static_cast<double>(format_context->duration) / AV_TIME_BASE;
ExtractDictionary(format_context->metadata);
for (unsigned int i = 0; i < format_context->nb_streams; ++i) {
AVStream* stream = format_context->streams[i];
if (!stream)
continue;
// Ignore attached pictures for metadata extraction.
if ((stream->disposition & AV_DISPOSITION_ATTACHED_PIC) != 0)
continue;
// Extract dictionary from streams also. Needed for containers that attach
// metadata to contained streams instead the container itself, like OGG.
ExtractDictionary(stream->metadata);
if (!stream->codec)
continue;
// Extract dimensions of largest stream that's not an attached picture.
if (stream->codec->width > 0 && stream->codec->width > width_ &&
stream->codec->height > 0 && stream->codec->height > height_) {
width_ = stream->codec->width;
height_ = stream->codec->height;
}
}
extracted_ = true;
return true;
}
double AudioVideoMetadataExtractor::duration() const {
DCHECK(extracted_);
return duration_;
}
int AudioVideoMetadataExtractor::width() const {
DCHECK(extracted_);
return width_;
}
int AudioVideoMetadataExtractor::height() const {
DCHECK(extracted_);
return height_;
}
const std::string& AudioVideoMetadataExtractor::album() const {
DCHECK(extracted_);
return album_;
}
const std::string& AudioVideoMetadataExtractor::artist() const {
DCHECK(extracted_);
return artist_;
}
const std::string& AudioVideoMetadataExtractor::comment() const {
DCHECK(extracted_);
return comment_;
}
const std::string& AudioVideoMetadataExtractor::copyright() const {
DCHECK(extracted_);
return copyright_;
}
const std::string& AudioVideoMetadataExtractor::date() const {
DCHECK(extracted_);
return date_;
}
int AudioVideoMetadataExtractor::disc() const {
DCHECK(extracted_);
return disc_;
}
const std::string& AudioVideoMetadataExtractor::encoder() const {
DCHECK(extracted_);
return encoder_;
}
const std::string& AudioVideoMetadataExtractor::encoded_by() const {
DCHECK(extracted_);
return encoded_by_;
}
const std::string& AudioVideoMetadataExtractor::genre() const {
DCHECK(extracted_);
return genre_;
}
const std::string& AudioVideoMetadataExtractor::language() const {
DCHECK(extracted_);
return language_;
}
const std::string& AudioVideoMetadataExtractor::title() const {
DCHECK(extracted_);
return title_;
}
int AudioVideoMetadataExtractor::track() const {
DCHECK(extracted_);
return track_;
}
void AudioVideoMetadataExtractor::ExtractDictionary(AVDictionary* metadata) {
if (!metadata)
return;
AVDictionaryEntry* tag = NULL;
while ((tag = av_dict_get(metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {
if (ExtractString(tag, "album", &album_)) continue;
if (ExtractString(tag, "artist", &artist_)) continue;
if (ExtractString(tag, "comment", &comment_)) continue;
if (ExtractString(tag, "copyright", &copyright_)) continue;
if (ExtractString(tag, "date", &date_)) continue;
if (ExtractInt(tag, "disc", &disc_)) continue;
if (ExtractString(tag, "encoder", &encoder_)) continue;
if (ExtractString(tag, "encoded_by", &encoded_by_)) continue;
if (ExtractString(tag, "genre", &genre_)) continue;
if (ExtractString(tag, "language", &language_)) continue;
if (ExtractString(tag, "title", &title_)) continue;
if (ExtractInt(tag, "track", &track_)) continue;
}
}
} // namespace media
// 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 MEDIA_BASE_AUDIO_VIDEO_METADATA_EXTRACTOR_H_
#define MEDIA_BASE_AUDIO_VIDEO_METADATA_EXTRACTOR_H_
#include <string>
#include "base/basictypes.h"
#include "media/base/media_export.h"
struct AVDictionary;
namespace media {
class DataSource;
// This class extracts a string dictionary of metadata tags for audio and video
// files. It also provides the format name.
class MEDIA_EXPORT AudioVideoMetadataExtractor {
public:
AudioVideoMetadataExtractor();
~AudioVideoMetadataExtractor();
// Returns whether or not the fields were successfully extracted. Should only
// be called once.
bool Extract(DataSource* source);
// Returns -1 if we cannot extract the duration. In seconds.
double duration() const;
// Returns -1 for containers without video.
int width() const;
int height() const;
// Returns -1 or an empty string if the value is undefined.
const std::string& album() const;
const std::string& artist() const;
const std::string& comment() const;
const std::string& copyright() const;
const std::string& date() const;
int disc() const;
const std::string& encoder() const;
const std::string& encoded_by() const;
const std::string& genre() const;
const std::string& language() const;
const std::string& title() const;
int track() const;
private:
void ExtractDictionary(AVDictionary* metadata);
bool extracted_;
int duration_;
int width_;
int height_;
std::string album_;
std::string artist_;
std::string comment_;
std::string copyright_;
std::string date_;
int disc_;
std::string encoder_;
std::string encoded_by_;
std::string genre_;
std::string language_;
std::string title_;
int track_;
DISALLOW_COPY_AND_ASSIGN(AudioVideoMetadataExtractor);
};
} // namespace media
#endif // MEDIA_BASE_AUDIO_VIDEO_METADATA_EXTRACTOR_H_
// 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/logging.h"
#include "base/memory/scoped_ptr.h"
#include "build/build_config.h"
#include "media/base/audio_video_metadata_extractor.h"
#include "media/base/test_data_util.h"
#include "media/filters/file_data_source.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
scoped_ptr<AudioVideoMetadataExtractor> GetExtractor(
const std::string& filename,
bool expected_result,
double expected_duration,
int expected_width,
int expected_height) {
FileDataSource source;
EXPECT_TRUE(source.Initialize(GetTestDataFilePath(filename)));
scoped_ptr<AudioVideoMetadataExtractor> extractor(
new AudioVideoMetadataExtractor);
bool extracted = extractor->Extract(&source);
EXPECT_EQ(expected_result, extracted);
if (!extracted)
return extractor.Pass();
EXPECT_EQ(expected_duration, extractor->duration());
EXPECT_EQ(expected_width, extractor->width());
EXPECT_EQ(expected_height, extractor->height());
return extractor.Pass();
}
TEST(AudioVideoMetadataExtractorTest, InvalidFile) {
GetExtractor("ten_byte_file", false, 0, -1, -1);
}
TEST(AudioVideoMetadataExtractorTest, AudioOGG) {
scoped_ptr<AudioVideoMetadataExtractor> extractor =
GetExtractor("9ch.ogg", true, 0, -1, -1);
EXPECT_EQ("Processed by SoX", extractor->comment());
}
TEST(AudioVideoMetadataExtractorTest, AudioWAV) {
scoped_ptr<AudioVideoMetadataExtractor> extractor =
GetExtractor("sfx_u8.wav", true, 0, -1, -1);
EXPECT_EQ("Lavf54.37.100", extractor->encoder());
EXPECT_EQ("Amadeus Pro", extractor->encoded_by());
}
TEST(AudioVideoMetadataExtractorTest, VideoWebM) {
scoped_ptr<AudioVideoMetadataExtractor> extractor =
GetExtractor("bear-320x240-multitrack.webm", true, 2, 320, 240);
EXPECT_EQ("Lavf53.9.0", extractor->encoder());
}
#if defined(USE_PROPRIETARY_CODECS)
TEST(AudioVideoMetadataExtractorTest, AudioMP3) {
scoped_ptr<AudioVideoMetadataExtractor> extractor =
GetExtractor("id3_png_test.mp3", true, 1, -1, -1);
EXPECT_EQ("Airbag", extractor->title());
EXPECT_EQ("Radiohead", extractor->artist());
EXPECT_EQ("OK Computer", extractor->album());
EXPECT_EQ(1, extractor->track());
EXPECT_EQ("Alternative", extractor->genre());
EXPECT_EQ("1997", extractor->date());
EXPECT_EQ("Lavf54.4.100", extractor->encoder());
}
#endif
} // namespace media
...@@ -239,6 +239,8 @@ ...@@ -239,6 +239,8 @@
'base/audio_splicer.h', 'base/audio_splicer.h',
'base/audio_timestamp_helper.cc', 'base/audio_timestamp_helper.cc',
'base/audio_timestamp_helper.h', 'base/audio_timestamp_helper.h',
'base/audio_video_metadata_extractor.cc',
'base/audio_video_metadata_extractor.h',
'base/bind_to_loop.h', 'base/bind_to_loop.h',
'base/bit_reader.cc', 'base/bit_reader.cc',
'base/bit_reader.h', 'base/bit_reader.h',
...@@ -510,6 +512,8 @@ ...@@ -510,6 +512,8 @@
}, { # media_use_ffmpeg==0 }, { # media_use_ffmpeg==0
# Exclude the sources that depend on ffmpeg. # Exclude the sources that depend on ffmpeg.
'sources!': [ 'sources!': [
'base/audio_video_metadata_extractor.cc',
'base/audio_video_metadata_extractor.h',
'base/container_names.cc', 'base/container_names.cc',
'base/container_names.h', 'base/container_names.h',
'base/media_file_checker.cc', 'base/media_file_checker.cc',
...@@ -945,6 +949,7 @@ ...@@ -945,6 +949,7 @@
'base/audio_renderer_mixer_unittest.cc', 'base/audio_renderer_mixer_unittest.cc',
'base/audio_splicer_unittest.cc', 'base/audio_splicer_unittest.cc',
'base/audio_timestamp_helper_unittest.cc', 'base/audio_timestamp_helper_unittest.cc',
'base/audio_video_metadata_extractor_unittest.cc',
'base/bind_to_loop_unittest.cc', 'base/bind_to_loop_unittest.cc',
'base/bit_reader_unittest.cc', 'base/bit_reader_unittest.cc',
'base/callback_holder.h', 'base/callback_holder.h',
...@@ -1033,6 +1038,7 @@ ...@@ -1033,6 +1038,7 @@
], ],
}, { # media_use_ffmpeg== 0 }, { # media_use_ffmpeg== 0
'sources!': [ 'sources!': [
'base/audio_video_metadata_extractor_unittest.cc',
'base/media_file_checker_unittest.cc', 'base/media_file_checker_unittest.cc',
], ],
}], }],
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment