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 
27 #include <gst/gst.h>
28 
29 #include <exception>
30 #include <future>
31 
32 namespace gstreamer
33 {
35 {
36 public:
37  static const std::map<std::string, std::string>& gstreamer_to_mpris_tag_lut()
38  {
39  static const std::map<std::string, std::string> lut
40  {
41  {GST_TAG_ALBUM, std::string{xesam::Album::name}},
42  {GST_TAG_ALBUM_ARTIST, std::string{xesam::AlbumArtist::name}},
43  {GST_TAG_ARTIST, std::string{xesam::Artist::name}},
44  {GST_TAG_LYRICS, std::string{xesam::AsText::name}},
45  {GST_TAG_COMMENT, std::string{xesam::Comment::name}},
46  {GST_TAG_COMPOSER, std::string{xesam::Composer::name}},
47  {GST_TAG_DATE, std::string{xesam::ContentCreated::name}},
48  {GST_TAG_ALBUM_VOLUME_NUMBER, std::string{xesam::DiscNumber::name}},
49  {GST_TAG_GENRE, std::string{xesam::Genre::name}},
50  {GST_TAG_TITLE, std::string{xesam::Title::name}},
51  {GST_TAG_TRACK_NUMBER, std::string{xesam::TrackNumber::name}},
52  {GST_TAG_USER_RATING, std::string{xesam::UserRating::name}}
53  };
54 
55  return lut;
56  }
57 
58  static void on_tag_available(
61  {
62  namespace media = core::ubuntu::media;
63 
64  gst_tag_list_foreach(
65  tag.tag_list,
66  [](const GstTagList *list,
67  const gchar* tag,
68  gpointer user_data)
69  {
70  (void) list;
71 
72  auto md = static_cast<media::Track::MetaData*>(user_data);
73  std::stringstream ss;
74 
75  switch(gst_tag_get_type(tag))
76  {
77  case G_TYPE_BOOLEAN:
78  {
79  gboolean value;
80  if (gst_tag_list_get_boolean(list, tag, &value))
81  ss << value;
82  break;
83  }
84  case G_TYPE_INT:
85  {
86  gint value;
87  if (gst_tag_list_get_int(list, tag, &value))
88  ss << value;
89  break;
90  }
91  case G_TYPE_UINT:
92  {
93  guint value;
94  if (gst_tag_list_get_uint(list, tag, &value))
95  ss << value;
96  break;
97  }
98  case G_TYPE_INT64:
99  {
100  gint64 value;
101  if (gst_tag_list_get_int64(list, tag, &value))
102  ss << value;
103  break;
104  }
105  case G_TYPE_UINT64:
106  {
107  guint64 value;
108  if (gst_tag_list_get_uint64(list, tag, &value))
109  ss << value;
110  break;
111  }
112  case G_TYPE_FLOAT:
113  {
114  gfloat value;
115  if (gst_tag_list_get_float(list, tag, &value))
116  ss << value;
117  break;
118  }
119  case G_TYPE_DOUBLE:
120  {
121  double value;
122  if (gst_tag_list_get_double(list, tag, &value))
123  ss << value;
124  break;
125  }
126  case G_TYPE_STRING:
127  {
128  gchar* value;
129  if (gst_tag_list_get_string(list, tag, &value))
130  {
131  ss << value;
132  g_free(value);
133  }
134  break;
135  }
136  default:
137  break;
138  }
139 
140  (*md).set(
141  (gstreamer_to_mpris_tag_lut().count(tag) > 0 ? gstreamer_to_mpris_tag_lut().at(tag) : tag),
142  ss.str());
143  },
144  &md);
145  }
146 
148  : pipe(gst_pipeline_new("meta_data_extractor_pipeline")),
149  decoder(gst_element_factory_make ("uridecodebin", NULL)),
150  bus(gst_element_get_bus(pipe))
151  {
152  gst_bin_add(GST_BIN(pipe), decoder);
153 
154  auto sink = gst_element_factory_make ("fakesink", NULL);
155  gst_bin_add (GST_BIN (pipe), sink);
156 
157  g_signal_connect (decoder, "pad-added", G_CALLBACK (on_new_pad), sink);
158  }
159 
161  {
162  set_state_and_wait(GST_STATE_NULL);
163  gst_object_unref(pipe);
164  }
165 
166  bool set_state_and_wait(GstState new_state)
167  {
168  static const std::chrono::nanoseconds state_change_timeout
169  {
170  // We choose a quite high value here as tests are run under valgrind
171  // and gstreamer pipeline setup/state changes take longer in that scenario.
172  // The value does not negatively impact runtime performance.
173  std::chrono::milliseconds{5000}
174  };
175 
176  auto ret = gst_element_set_state(pipe, new_state);
177 
178  bool result = false; GstState current, pending;
179  switch(ret)
180  {
181  case GST_STATE_CHANGE_FAILURE:
182  result = false; break;
183  case GST_STATE_CHANGE_NO_PREROLL:
184  case GST_STATE_CHANGE_SUCCESS:
185  result = true; break;
186  case GST_STATE_CHANGE_ASYNC:
187  result = GST_STATE_CHANGE_SUCCESS == gst_element_get_state(
188  pipe,
189  &current,
190  &pending,
191  state_change_timeout.count());
192  break;
193  }
194 
195  return result;
196  }
197 
199  {
200  if (!gst_uri_is_valid(uri.c_str()))
201  throw std::runtime_error("Invalid uri");
202 
204  std::promise<core::ubuntu::media::Track::MetaData> promise;
205  std::future<core::ubuntu::media::Track::MetaData> future{promise.get_future()};
206 
207  core::ScopedConnection on_new_message_connection
208  {
209  bus.on_new_message.connect(
210  [&](const gstreamer::Bus::Message& msg)
211  {
212  //std::cout << __PRETTY_FUNCTION__ << gst_message_type_get_name(msg.type) << std::endl;
213  if (msg.type == GST_MESSAGE_TAG)
214  {
215  MetaDataExtractor::on_tag_available(msg.detail.tag, meta_data);
216  } else if (msg.type == GST_MESSAGE_ASYNC_DONE)
217  {
218  promise.set_value(meta_data);
219  }
220  })
221  };
222 
223  g_object_set(decoder, "uri", uri.c_str(), NULL);
224  gst_element_set_state(pipe, GST_STATE_PAUSED);
225 
226  if (std::future_status::ready != future.wait_for(std::chrono::seconds(4)))
227  {
228  set_state_and_wait(GST_STATE_NULL);
229  throw std::runtime_error("Problem extracting meta data for track");
230  } else
231  {
232  set_state_and_wait(GST_STATE_NULL);
233  }
234 
235  return future.get();
236  }
237 
238 private:
239  static void on_new_pad(GstElement*, GstPad* pad, GstElement* fakesink)
240  {
241  GstPad *sinkpad;
242 
243  sinkpad = gst_element_get_static_pad (fakesink, "sink");
244 
245  if (!gst_pad_is_linked (sinkpad)) {
246  if (gst_pad_link (pad, sinkpad) != GST_PAD_LINK_OK)
247  g_error ("Failed to link pads!");
248  }
249 
250  gst_object_unref (sinkpad);
251  }
252 
253  GstElement* pipe;
254  GstElement* decoder;
255  Bus bus;
256 };
257 }
258 
259 #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)