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
34namespace gstreamer
35{
37{
38public:
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
254private:
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_
std::string UriType
Definition: track.h:40
bool set_state_and_wait(GstState new_state)
static void on_tag_available(const gstreamer::Bus::Message::Detail::Tag &tag, core::ubuntu::media::Track::MetaData &md)
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)
Definition: bus.h:34
GstMessageType type
Definition: bus.h:181