Music Hub  ..
A session-wide music playback service
meta_data_extractor.h
Go to the documentation of this file.
1 /*
2  * Copyright © 2013 Canonical Ltd.
3  *
4  * This program is free software: you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License version 3,
6  * as published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  *
16  * Authored by: Thomas Voß <thomas.voss@canonical.com>
17  */
18 
19 #ifndef GSTREAMER_META_DATA_EXTRACTOR_H_
20 #define GSTREAMER_META_DATA_EXTRACTOR_H_
21 
22 #include "../engine.h"
23 #include "../xesam.h"
24 
25 #include "bus.h"
26 
28 
29 #include <gst/gst.h>
30 
31 #include <exception>
32 #include <future>
33 
34 namespace gstreamer
35 {
37 {
38 public:
39  static const std::map<std::string, std::string>& gstreamer_to_mpris_tag_lut()
40  {
41  static const std::map<std::string, std::string> lut
42  {
43  {GST_TAG_ALBUM, std::string{xesam::Album::name}},
44  {GST_TAG_ALBUM_ARTIST, std::string{xesam::AlbumArtist::name}},
45  {GST_TAG_ARTIST, std::string{xesam::Artist::name}},
46  {GST_TAG_LYRICS, std::string{xesam::AsText::name}},
47  {GST_TAG_COMMENT, std::string{xesam::Comment::name}},
48  {GST_TAG_COMPOSER, std::string{xesam::Composer::name}},
49  {GST_TAG_DATE, std::string{xesam::ContentCreated::name}},
50  {GST_TAG_ALBUM_VOLUME_NUMBER, std::string{xesam::DiscNumber::name}},
51  {GST_TAG_GENRE, std::string{xesam::Genre::name}},
52  {GST_TAG_TITLE, std::string{xesam::Title::name}},
53  {GST_TAG_TRACK_NUMBER, std::string{xesam::TrackNumber::name}},
54  {GST_TAG_USER_RATING, std::string{xesam::UserRating::name}},
55  // Below this line are custom entries related but not directly from
56  // the MPRIS spec:
57  {GST_TAG_IMAGE, std::string{tags::Image::name}},
58  {GST_TAG_PREVIEW_IMAGE, std::string{tags::PreviewImage::name}}
59  };
60 
61  return lut;
62  }
63 
64  static void on_tag_available(
67  {
68  namespace media = core::ubuntu::media;
69 
70  gst_tag_list_foreach(
71  tag.tag_list,
72  [](const GstTagList *list,
73  const gchar* tag,
74  gpointer user_data)
75  {
76  (void) list;
77 
78  auto md = static_cast<media::Track::MetaData*>(user_data);
79  std::stringstream ss;
80 
81  switch (gst_tag_get_type(tag))
82  {
83  case G_TYPE_BOOLEAN:
84  {
85  gboolean value;
86  if (gst_tag_list_get_boolean(list, tag, &value))
87  ss << value;
88  break;
89  }
90  case G_TYPE_INT:
91  {
92  gint value;
93  if (gst_tag_list_get_int(list, tag, &value))
94  ss << value;
95  break;
96  }
97  case G_TYPE_UINT:
98  {
99  guint value;
100  if (gst_tag_list_get_uint(list, tag, &value))
101  ss << value;
102  break;
103  }
104  case G_TYPE_INT64:
105  {
106  gint64 value;
107  if (gst_tag_list_get_int64(list, tag, &value))
108  ss << value;
109  break;
110  }
111  case G_TYPE_UINT64:
112  {
113  guint64 value;
114  if (gst_tag_list_get_uint64(list, tag, &value))
115  ss << value;
116  break;
117  }
118  case G_TYPE_FLOAT:
119  {
120  gfloat value;
121  if (gst_tag_list_get_float(list, tag, &value))
122  ss << value;
123  break;
124  }
125  case G_TYPE_DOUBLE:
126  {
127  double value;
128  if (gst_tag_list_get_double(list, tag, &value))
129  ss << value;
130  break;
131  }
132  case G_TYPE_STRING:
133  {
134  gchar* value;
135  if (gst_tag_list_get_string(list, tag, &value))
136  {
137  ss << value;
138  g_free(value);
139  }
140  break;
141  }
142  default:
143  break;
144  }
145 
146  const bool has_tag_from_lut = (gstreamer_to_mpris_tag_lut().count(tag) > 0);
147  const std::string tag_name{(has_tag_from_lut) ?
148  gstreamer_to_mpris_tag_lut().at(tag) : tag};
149 
150 
151  // Specific handling for the following tag types:
152  if (tag_name == tags::PreviewImage::name)
153  ss << "true";
154  if (tag_name == tags::Image::name)
155  ss << "true";
156 
157  (*md).set((has_tag_from_lut ?
158  gstreamer_to_mpris_tag_lut().at(tag) : tag), ss.str());
159  },
160  &md);
161  }
162 
164  : pipe(gst_pipeline_new("meta_data_extractor_pipeline")),
165  decoder(gst_element_factory_make ("uridecodebin", NULL)),
166  bus(gst_element_get_bus(pipe))
167  {
168  gst_bin_add(GST_BIN(pipe), decoder);
169 
170  auto sink = gst_element_factory_make ("fakesink", NULL);
171  gst_bin_add (GST_BIN (pipe), sink);
172 
173  g_signal_connect (decoder, "pad-added", G_CALLBACK (on_new_pad), sink);
174  }
175 
177  {
178  set_state_and_wait(GST_STATE_NULL);
179  gst_object_unref(pipe);
180  }
181 
182  bool set_state_and_wait(GstState new_state)
183  {
184  static const std::chrono::nanoseconds state_change_timeout
185  {
186  // We choose a quite high value here as tests are run under valgrind
187  // and gstreamer pipeline setup/state changes take longer in that scenario.
188  // The value does not negatively impact runtime performance.
189  std::chrono::milliseconds{5000}
190  };
191 
192  auto ret = gst_element_set_state(pipe, new_state);
193 
194  bool result = false; GstState current, pending;
195  switch(ret)
196  {
197  case GST_STATE_CHANGE_FAILURE:
198  result = false; break;
199  case GST_STATE_CHANGE_NO_PREROLL:
200  case GST_STATE_CHANGE_SUCCESS:
201  result = true; break;
202  case GST_STATE_CHANGE_ASYNC:
203  result = GST_STATE_CHANGE_SUCCESS == gst_element_get_state(
204  pipe,
205  &current,
206  &pending,
207  state_change_timeout.count());
208  break;
209  }
210 
211  return result;
212  }
213 
215  {
216  if (!gst_uri_is_valid(uri.c_str()))
217  throw std::runtime_error("Invalid uri");
218 
220  std::promise<core::ubuntu::media::Track::MetaData> promise;
221  std::future<core::ubuntu::media::Track::MetaData> future{promise.get_future()};
222 
223  core::ScopedConnection on_new_message_connection
224  {
225  bus.on_new_message.connect(
226  [&](const gstreamer::Bus::Message& msg)
227  {
228  //std::cout << __PRETTY_FUNCTION__ << gst_message_type_get_name(msg.type) << std::endl;
229  if (msg.type == GST_MESSAGE_TAG)
230  {
231  MetaDataExtractor::on_tag_available(msg.detail.tag, meta_data);
232  } else if (msg.type == GST_MESSAGE_ASYNC_DONE)
233  {
234  promise.set_value(meta_data);
235  }
236  })
237  };
238 
239  g_object_set(decoder, "uri", uri.c_str(), NULL);
240  gst_element_set_state(pipe, GST_STATE_PAUSED);
241 
242  if (std::future_status::ready != future.wait_for(std::chrono::seconds(4)))
243  {
244  set_state_and_wait(GST_STATE_NULL);
245  throw std::runtime_error("Problem extracting meta data for track");
246  } else
247  {
248  set_state_and_wait(GST_STATE_NULL);
249  }
250 
251  return future.get();
252  }
253 
254 private:
255  static void on_new_pad(GstElement*, GstPad* pad, GstElement* fakesink)
256  {
257  GstPad *sinkpad;
258 
259  sinkpad = gst_element_get_static_pad (fakesink, "sink");
260 
261  if (!gst_pad_is_linked (sinkpad)) {
262  if (gst_pad_link (pad, sinkpad) != GST_PAD_LINK_OK)
263  g_error ("Failed to link pads!");
264  }
265 
266  gst_object_unref (sinkpad);
267  }
268 
269  GstElement* pipe;
270  GstElement* decoder;
271  Bus bus;
272 };
273 }
274 
275 #endif // GSTREAMER_META_DATA_EXTRACTOR_H_
core::Signal< Message > on_new_message
Definition: bus.h:332
GstMessageType type
Definition: bus.h:181
Definition: bus.h:33
static const std::map< std::string, std::string > & gstreamer_to_mpris_tag_lut()
core::ubuntu::media::Track::MetaData meta_data_for_track_with_uri(const core::ubuntu::media::Track::UriType &uri)
static void on_tag_available(const gstreamer::Bus::Message::Detail::Tag &tag, core::ubuntu::media::Track::MetaData &md)
std::string UriType
Definition: track.h:40
bool set_state_and_wait(GstState new_state)