Music Hub ..
A session-wide music playback service
 
Loading...
Searching...
No Matches
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
35namespace gstreamer
36{
38{
39public:
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 {
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
247private:
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
bool set_state_and_wait(GstState new_state)
static void on_tag_available(const gstreamer::Bus::Message::Detail::Tag &tag, QVariantMap *md)
static const std::map< std::string, std::string > & gstreamer_to_mpris_tag_lut()
void meta_data_for_track_with_uri(const QUrl &uri, const Callback &cb)
std::function< void(const QVariantMap &)> Callback
Definition engine.h:64