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