1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "media/base/audio_video_metadata_extractor.h"
6
7#include "base/bind.h"
8#include "base/strings/string_number_conversions.h"
9#include "base/strings/string_util.h"
10#include "base/time/time.h"
11#include "media/ffmpeg/ffmpeg_common.h"
12#include "media/filters/blocking_url_protocol.h"
13#include "media/filters/ffmpeg_glue.h"
14
15namespace media {
16
17namespace {
18
19void OnError(bool* succeeded) {
20  *succeeded = false;
21}
22
23// Returns true if the |tag| matches |expected_key|.
24bool ExtractString(AVDictionaryEntry* tag, const char* expected_key,
25                   std::string* destination) {
26  if (!LowerCaseEqualsASCII(std::string(tag->key), expected_key))
27    return false;
28
29  if (destination->empty())
30    *destination = tag->value;
31
32  return true;
33}
34
35// Returns true if the |tag| matches |expected_key|.
36bool ExtractInt(AVDictionaryEntry* tag, const char* expected_key,
37                int* destination) {
38  if (!LowerCaseEqualsASCII(std::string(tag->key), expected_key))
39    return false;
40
41  int temporary = -1;
42  if (*destination < 0 && base::StringToInt(tag->value, &temporary) &&
43      temporary >= 0) {
44    *destination = temporary;
45  }
46
47  return true;
48}
49
50// Set attached image size limit to 4MB. Chosen arbitrarily.
51const int kAttachedImageSizeLimit = 4 * 1024 * 1024;
52
53}  // namespace
54
55AudioVideoMetadataExtractor::StreamInfo::StreamInfo() {}
56
57AudioVideoMetadataExtractor::StreamInfo::~StreamInfo() {}
58
59AudioVideoMetadataExtractor::AudioVideoMetadataExtractor()
60    : extracted_(false),
61      duration_(-1),
62      width_(-1),
63      height_(-1),
64      disc_(-1),
65      rotation_(-1),
66      track_(-1) {
67}
68
69AudioVideoMetadataExtractor::~AudioVideoMetadataExtractor() {
70}
71
72bool AudioVideoMetadataExtractor::Extract(DataSource* source,
73                                          bool extract_attached_images) {
74  DCHECK(!extracted_);
75
76  bool read_ok = true;
77  media::BlockingUrlProtocol protocol(source, base::Bind(&OnError, &read_ok));
78  media::FFmpegGlue glue(&protocol);
79  AVFormatContext* format_context = glue.format_context();
80
81  if (!glue.OpenContext())
82    return false;
83
84  if (!read_ok)
85    return false;
86
87  if (!format_context->iformat)
88    return false;
89
90  if (avformat_find_stream_info(format_context, NULL) < 0)
91    return false;
92
93  if (format_context->duration != AV_NOPTS_VALUE)
94    duration_ = static_cast<double>(format_context->duration) / AV_TIME_BASE;
95
96  stream_infos_.push_back(StreamInfo());
97  StreamInfo& container_info = stream_infos_.back();
98  container_info.type = format_context->iformat->name;
99  ExtractDictionary(format_context->metadata, &container_info.tags);
100
101  for (unsigned int i = 0; i < format_context->nb_streams; ++i) {
102    stream_infos_.push_back(StreamInfo());
103    StreamInfo& info = stream_infos_.back();
104
105    AVStream* stream = format_context->streams[i];
106    if (!stream)
107      continue;
108
109    // Extract dictionary from streams also. Needed for containers that attach
110    // metadata to contained streams instead the container itself, like OGG.
111    ExtractDictionary(stream->metadata, &info.tags);
112
113    if (!stream->codec)
114      continue;
115
116    info.type = avcodec_get_name(stream->codec->codec_id);
117
118    // Extract dimensions of largest stream that's not an attached image.
119    if (stream->codec->width > 0 && stream->codec->width > width_ &&
120        stream->codec->height > 0 && stream->codec->height > height_) {
121      width_ = stream->codec->width;
122      height_ = stream->codec->height;
123    }
124
125    // Extract attached image if requested.
126    if (extract_attached_images &&
127        stream->disposition == AV_DISPOSITION_ATTACHED_PIC &&
128        stream->attached_pic.size > 0 &&
129        stream->attached_pic.size <= kAttachedImageSizeLimit &&
130        stream->attached_pic.data != NULL) {
131      attached_images_bytes_.push_back(std::string());
132      attached_images_bytes_.back().assign(
133          reinterpret_cast<const char*>(stream->attached_pic.data),
134          stream->attached_pic.size);
135    }
136  }
137
138  extracted_ = true;
139  return true;
140}
141
142double AudioVideoMetadataExtractor::duration() const {
143  DCHECK(extracted_);
144  return duration_;
145}
146
147int AudioVideoMetadataExtractor::width() const {
148  DCHECK(extracted_);
149  return width_;
150}
151
152int AudioVideoMetadataExtractor::height() const {
153  DCHECK(extracted_);
154  return height_;
155}
156
157int AudioVideoMetadataExtractor::rotation() const {
158  DCHECK(extracted_);
159  return rotation_;
160}
161
162const std::string& AudioVideoMetadataExtractor::album() const {
163  DCHECK(extracted_);
164  return album_;
165}
166
167const std::string& AudioVideoMetadataExtractor::artist() const {
168  DCHECK(extracted_);
169  return artist_;
170}
171
172const std::string& AudioVideoMetadataExtractor::comment() const {
173  DCHECK(extracted_);
174  return comment_;
175}
176
177const std::string& AudioVideoMetadataExtractor::copyright() const {
178  DCHECK(extracted_);
179  return copyright_;
180}
181
182const std::string& AudioVideoMetadataExtractor::date() const {
183  DCHECK(extracted_);
184  return date_;
185}
186
187int AudioVideoMetadataExtractor::disc() const {
188  DCHECK(extracted_);
189  return disc_;
190}
191
192const std::string& AudioVideoMetadataExtractor::encoder() const {
193  DCHECK(extracted_);
194  return encoder_;
195}
196
197const std::string& AudioVideoMetadataExtractor::encoded_by() const {
198  DCHECK(extracted_);
199  return encoded_by_;
200}
201
202const std::string& AudioVideoMetadataExtractor::genre() const {
203  DCHECK(extracted_);
204  return genre_;
205}
206
207const std::string& AudioVideoMetadataExtractor::language() const {
208  DCHECK(extracted_);
209  return language_;
210}
211
212const std::string& AudioVideoMetadataExtractor::title() const {
213  DCHECK(extracted_);
214  return title_;
215}
216
217int AudioVideoMetadataExtractor::track() const {
218  DCHECK(extracted_);
219  return track_;
220}
221
222const std::vector<AudioVideoMetadataExtractor::StreamInfo>&
223AudioVideoMetadataExtractor::stream_infos() const {
224  DCHECK(extracted_);
225  return stream_infos_;
226}
227
228const std::vector<std::string>&
229AudioVideoMetadataExtractor::attached_images_bytes() const {
230  DCHECK(extracted_);
231  return attached_images_bytes_;
232}
233
234void AudioVideoMetadataExtractor::ExtractDictionary(
235    AVDictionary* metadata, TagDictionary* raw_tags) {
236  if (!metadata)
237    return;
238
239  for (AVDictionaryEntry* tag =
240           av_dict_get(metadata, "", NULL, AV_DICT_IGNORE_SUFFIX);
241       tag; tag = av_dict_get(metadata, "", tag, AV_DICT_IGNORE_SUFFIX)) {
242    if (raw_tags->find(tag->key) == raw_tags->end())
243      (*raw_tags)[tag->key] = tag->value;
244
245    if (ExtractInt(tag, "rotate", &rotation_)) continue;
246    if (ExtractString(tag, "album", &album_)) continue;
247    if (ExtractString(tag, "artist", &artist_)) continue;
248    if (ExtractString(tag, "comment", &comment_)) continue;
249    if (ExtractString(tag, "copyright", &copyright_)) continue;
250    if (ExtractString(tag, "date", &date_)) continue;
251    if (ExtractInt(tag, "disc", &disc_)) continue;
252    if (ExtractString(tag, "encoder", &encoder_)) continue;
253    if (ExtractString(tag, "encoded_by", &encoded_by_)) continue;
254    if (ExtractString(tag, "genre", &genre_)) continue;
255    if (ExtractString(tag, "language", &language_)) continue;
256    if (ExtractString(tag, "title", &title_)) continue;
257    if (ExtractInt(tag, "track", &track_)) continue;
258  }
259}
260
261}  // namespace media
262